package com.lifesting.tool.j2as3.action;

import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;

import com.lifesting.tool.j2as3.impose.IPersistent;
import com.lifesting.tool.j2as3.visitor.OverLoadMethodVisitor;
import com.lifesting.tool.j2as3.visitor.IdGenerator;
import com.lifesting.tool.j2as3.visitor.SubTypeVisitor;
import com.lifesting.tool.j2as3.visitor.TransformVisitor;
import com.lifesting.tool.j2as3.visitor.TypeRelatioShipVisitor;
import com.lifesting.tool.j2as3.visitor.OverLoadMethodVisitor.OverloadMethodDescriptor;
import com.lifesting.tool.j2as3.visitor.SubTypeVisitor.SubTypeDescriptor;

public class Java2ActionScript3Action implements IObjectActionDelegate,
		IEditorActionDelegate {
	private IWorkbenchPart workbenchPart;
	private IJavaElement javaElement;
	private ASTParser parser;
	private OverLoadMethodVisitor constructorVisitor;
	private TransformVisitor transformVisitor;
	private SubTypeVisitor subTypeVisitor;
	private TypeRelatioShipVisitor rsVisitor;
	private NodeWriter nodeWriter;
	public Java2ActionScript3Action() {
		parser = ASTParser.newParser(AST.JLS3);
		nodeWriter = new NodeWriter();
		constructorVisitor = new OverLoadMethodVisitor();
		transformVisitor = new TransformVisitor(nodeWriter);
		subTypeVisitor = new SubTypeVisitor();
		rsVisitor = new TypeRelatioShipVisitor();
	}

	@Override
	public void setActivePart(IAction action, IWorkbenchPart targetPart) {
		this.workbenchPart = targetPart;
	}

	@Override
	public void run(IAction action) {
		if (javaElement== null) return;
		try{
			final IPath target_path = getTargetRoot();
			if (target_path == null)
				return;
			
			final MessageConsoleStream console = getMessageConsole();
			console.println("Start transformation");
			transformVisitor.setLogger(new ConsoleLogger(console));
			workbenchPart.getSite().getPage().getWorkbenchWindow().getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress(){
				@Override
				public void run(final IProgressMonitor monitor)
						throws InvocationTargetException, InterruptedException {
					Collection<? extends ITypeRoot> units;
					try {
						monitor.beginTask("Transform java files to ActionScript", 6);
						units = userFiltered();
//						showUnits(units);
						monitor.worked(1);
						collectOverloadedConstructors(units,monitor);
						monitor.worked(1);
						console.println("collected all constructor methods ");
						showConstructors();
						collectSubType(units,monitor);
//						showSubTypes();
						monitor.worked(1);
						String[] rootPackageFragments = Util.getRootPackageFragments(javaElement);
						rsVisitor.setSubTypeDescriptors(subTypeVisitor.getSubTypes());
						rsVisitor.setRootPackages(rootPackageFragments);
						prepareRelationShip(units,monitor);
//						showRelationShip();
						monitor.worked(1);
						transformVisitor.setOverloadConstructor(constructorVisitor.getDescriptors());
						transformVisitor.setSubTypeDescriptors(subTypeVisitor.getSubTypes());
						transformVisitor.setOverrideMethods(rsVisitor.getOverrideMethod());
						transformVisitor.setResolvedSignature(rsVisitor.getResolvedSignature());
						transformVisitor.setNeedSetPointer(rsVisitor.getNeedSetPointer());
						transformVisitor.setPeristent(new IPersistent(){
							@Override
							public void save(IPath path, String content) throws CoreException {
								IPath full_path = target_path.append(path);
								IFile saving_file = ResourcesPlugin.getWorkspace().getRoot().getFile(full_path);
								IFolder parent_folder = (IFolder) saving_file.getParent();
								if (!parent_folder.exists())
									Util.cascadeCreateFolder(parent_folder,monitor);
								if (saving_file.exists()) {
									saving_file.setContents(new ByteArrayInputStream(content.getBytes()), true, false,
											monitor);
								}else{
									saving_file.create(new ByteArrayInputStream(content.getBytes()), true, monitor);
								}
							}});
						
						transformVisitor.setSrcPackages(rootPackageFragments);
						monitor.worked(1);
//						genAsFiles(units,monitor);
						copyResources(target_path,monitor);
						monitor.worked(1);
						monitor.done();
						console.println("Over");
					} catch (JavaModelException e) {
						throw new InvocationTargetException(e);
					} catch (CoreException e) {
						throw new InvocationTargetException(e);
					}
				}});
			
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			constructorVisitor.reset();
			transformVisitor.reset();
			subTypeVisitor.reset();
			nodeWriter.reset();
			rsVisitor.reset();
			IdGenerator.renew();
		}
	}
	protected void showRelationShip() {
		Set<String> mark_override = rsVisitor.getOverrideMethod();
		for (String s : mark_override){
			System.out.println("Override "+s);
		}
		Set<String> needSetPointer = rsVisitor.getNeedSetPointer();
		for (String s : needSetPointer){
			System.out.println("Set pointer : "+s);
		}
	}

	protected void prepareRelationShip(Collection<? extends ITypeRoot> units,
			IProgressMonitor monitor) {
		for (ITypeRoot tr : units){
			parser.setSource(tr);
			parser.setResolveBindings(true);
			monitor.subTask("preparing relationship for "+tr.getElementName());
			ASTNode root_node = parser.createAST(monitor);
			root_node.accept(rsVisitor);
		}
	}

	protected void showSubTypes() {
		Map<String, SubTypeDescriptor> subTypes = subTypeVisitor.getSubTypes();
		for (Iterator<String> iter = subTypes.keySet().iterator(); iter.hasNext();){		
			String sub_type_qn = iter.next();
			SubTypeDescriptor sd = subTypes.get(sub_type_qn);
			System.out.println(sub_type_qn+"{"+sd+"}");
		}
	}

	protected void collectSubType(Collection<? extends ITypeRoot> units,
			IProgressMonitor monitor) {
		for (ITypeRoot tr : units){
			parser.setSource(tr);
			ASTNode rn = parser.createAST(monitor);
			monitor.subTask("collect subtype for "+tr.getElementName());
			rn.accept(subTypeVisitor);
		}
	}

	private IPath getTargetRoot() {
		ContainerSelectionDialog csd = new ContainerSelectionDialog(workbenchPart
				.getSite().getShell(), null, true, "Select a target folder");
		if (csd.open() == Dialog.OK) {
			Object[] result = csd.getResult();
			return (IPath) result[0];
		}
		return null;
	}

	private MessageConsoleStream getMessageConsole() {
		MessageConsole console = Util.getMessageConsole();
		return console.newMessageStream();
	}

	private void showConstructors() {
		OverloadMethodDescriptor[] descriptors = constructorVisitor.getDescriptors();
		for (OverloadMethodDescriptor omd : descriptors){
			System.out.println(omd.getResource()+":"+omd.getMethod());
		}
//		for (Over)
//		{
//			String qn = iter.next();
//			System.out.println(qn+":"+descriptors.get(qn));
//		}
	}

	private void showUnits(Collection<? extends ITypeRoot> units) {
		for (ITypeRoot tr : units)
		{
			System.out.println(getFullName(tr));
		}
	}

	private String getFullName(ITypeRoot tr) {
		return ((IPackageFragment)tr.getParent()).getElementName()+"."+tr.getElementName();
	}

	private void copyResources(IPath target_path, IProgressMonitor monitor) throws CoreException {
		HashMap<Object, String> non_java = new HashMap<Object, String>();
		if (javaElement instanceof IJavaProject){
			IPackageFragmentRoot[] pfrs = ((IJavaProject)javaElement).getPackageFragmentRoots();
			for (int i = 0; i < pfrs.length; i++) {
				IJavaElement[] rt =  pfrs[i].getChildren();
				for (Object o : rt){
					if (o instanceof IPackageFragment){
					IPackageFragment ipf = (IPackageFragment) o;
					non_java.put(ipf.getNonJavaResources(), ipf.getElementName());
					
					}
				}
			}
		}else if (javaElement instanceof IPackageFragmentRoot){
			IJavaElement[] rt =((IPackageFragmentRoot)javaElement).getChildren();
			
			for (Object o: rt){
				if (o instanceof IPackageFragment){
					IPackageFragment ipf = (IPackageFragment) o;
				non_java.put(ipf.getNonJavaResources(),ipf.getElementName());
				}
			}
		}else if (javaElement instanceof IPackageFragment){
			non_java.put(((IPackageFragment)javaElement).getNonJavaResources(),((IPackageFragment)javaElement).getElementName());
		}
		for(Iterator<Object> ite = non_java.keySet().iterator(); ite.hasNext();){
			Object[] os = (Object[]) ite.next();
			IPath path = target_path.append(non_java.get(os).replace('.', '/'));
			IFolder folder = ResourcesPlugin.getWorkspace().getRoot().getFolder(path);
			if (!folder.exists())
				Util.cascadeCreateFolder(folder, monitor);
			for (Object o : os){
				if (o instanceof IFile)
				{
					IFile tf = folder.getFile( ((IFile)o).getName());
					if (!tf.exists()){
						tf.create(((IFile)o).getContents(), IResource.FORCE, monitor);
					}
				}
				else if (o instanceof IJarEntryResource) {
					IJarEntryResource jar_res = (IJarEntryResource) o;
					IFile tf = folder.getFile(jar_res.getName());
					if (jar_res.isFile() && !tf.exists()){
						tf.create(jar_res.getContents(), IResource.FORCE, monitor);
					}
				}
			}
		}
	}

	//
	private void genAsFiles(Collection<? extends ITypeRoot> units, IProgressMonitor monitor) {
		for (ITypeRoot tr : units){
			parser.setSource(tr);
			parser.setResolveBindings(true);
			ASTNode compilation_unit = parser.createAST(monitor);
			monitor.subTask("Transform file "+Util.getProjectStyle(tr));
			compilation_unit.accept(transformVisitor);
			transformVisitor.reset();
		}
	}



	private void collectOverloadedConstructors(Collection<? extends ITypeRoot> units, IProgressMonitor monitor) {
		for (ITypeRoot tr : units){
			parser.setSource(tr);
			parser.setResolveBindings(true);
			ASTNode rootNode = parser.createAST(monitor);
			monitor.subTask("collect constructor for "+tr.getElementName());
			rootNode.accept(constructorVisitor);
		}
	}

	private Collection<? extends ITypeRoot> userFiltered() throws JavaModelException {
		if (javaElement instanceof ITypeRoot) return Collections.<ITypeRoot>singleton((ITypeRoot)javaElement);
		if (javaElement instanceof IPackageFragment){
			return packageUnits((IPackageFragment)javaElement);
		}else if (javaElement instanceof IPackageFragmentRoot){
			Collection<? extends ITypeRoot> units = packageRootUnits((IPackageFragmentRoot)javaElement);
			return units;
		}else{ //project
			IPackageFragmentRoot[] pfrs = ((IJavaProject)javaElement).getPackageFragmentRoots();
			Collection<ITypeRoot> units = new ArrayList<ITypeRoot>(); 
			for (IPackageFragmentRoot root : pfrs){
				if(root.getKind() == IPackageFragmentRoot.K_SOURCE) //omit binary library
					units.addAll(packageRootUnits(root));
			}
			return units;
		}
	}

	private Collection<? extends ITypeRoot> packageRootUnits(
			IPackageFragmentRoot pfr) throws JavaModelException {
		List<ITypeRoot> types = new ArrayList<ITypeRoot>();
		IJavaElement[] prs = pfr.getChildren();
		for (IJavaElement pf : prs){
			types.addAll(packageUnits((IPackageFragment) pf));
		}
		return types;
	}

	private Collection<ITypeRoot> packageUnits(IPackageFragment pf) throws JavaModelException {
		List<ITypeRoot> units = new ArrayList<ITypeRoot>(pf.getChildren().length);
		for (IJavaElement je : pf.getChildren()){
			units.add((ITypeRoot)je);
		}
		return units;
		
	}

	@Override
	public void selectionChanged(IAction action, ISelection selection) {
		if (workbenchPart instanceof IEditorPart) return; //In compliation unit editor
		javaElement = null;
		if (selection instanceof StructuredSelection) {
			Object selected = ((StructuredSelection) selection)
					.getFirstElement();
			if (selected instanceof IJavaElement) {
				javaElement = (IJavaElement) selected;
			} else if (selected instanceof IAdaptable) {
				Object obj = ((IAdaptable) selected)
						.getAdapter(IJavaElement.class);
				if (obj instanceof IJavaElement)
					javaElement = (IJavaElement) obj;
			}
		}
		if (javaElement == null || !supportElement(javaElement)){
			action.setEnabled(false);
		}else{
			action.setEnabled(true);
		}
	}
	private boolean supportElement(IJavaElement element2) {
		int tid = element2.getElementType();

		return tid == IJavaElement.JAVA_PROJECT
				|| tid == IJavaElement.PACKAGE_FRAGMENT_ROOT
				|| tid == IJavaElement.PACKAGE_FRAGMENT
				|| tid == IJavaElement.COMPILATION_UNIT
				|| tid == IJavaElement.CLASS_FILE;
	}
	@Override
	public void setActiveEditor(IAction action, IEditorPart targetEditor) {
		workbenchPart = targetEditor;
		if (targetEditor instanceof CompilationUnitEditor || targetEditor instanceof ClassFileEditor){
			javaElement =  EditorUtility.getEditorInputJavaElement(targetEditor, true);
			action.setEnabled(true);
		}else{
			action.setEnabled(false);
		}

	}

}
